﻿// ByteArrayExtensions.cs
// Copyright © 2013 Alex Forster

namespace Ami.ByteArrayExtensions
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Diagnostics.Contracts;

	public static class ByteArrayExtensions
	{
		[DebuggerStepThrough]
		public static Byte[] Prepend( this Byte[] @this, Byte[] items )
		{
			Contract.Assert( items != null );

			var result = new Byte[@this.Length + items.Length];

			Buffer.BlockCopy( items, 0, result, 0, items.Length );
			Buffer.BlockCopy( @this, 0, result, items.Length, @this.Length );

			return result;
		}

		[DebuggerStepThrough]
		public static Byte[] Append( this Byte[] @this, Byte[] items )
		{
			Contract.Assert( items != null );

			var result = new Byte[@this.Length + items.Length];

			Buffer.BlockCopy( @this, 0, result, 0, @this.Length );
			Buffer.BlockCopy( items, 0, result, @this.Length, items.Length );

			return result;
		}

		[DebuggerStepThrough]
		public static Byte[] Slice( this Byte[] @this, Int32 start )
		{
			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( @this.Length );

			return @this.Slice( _start, _end );
		}

		[DebuggerStepThrough]
		public static Byte[] Slice( this Byte[] @this, Int32 start, Int32 end )
		{
			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( end < 0 ) ? ( @this.Length + end ) : ( end );

			if( _start < 0 || @this.Length < _start )
			{
				throw new ArgumentOutOfRangeException( "start" );
			}

			if( _end < _start || @this.Length < _end )
			{
				throw new ArgumentOutOfRangeException( "end" );
			}

			var result = new Byte[_end - _start];

			Buffer.BlockCopy( @this, _start, result, 0, result.Length );

			return result;
		}

		[DebuggerStepThrough]
		public static Int32 Find( this Byte[] @this, Byte[] needle, Int32 start = 0 )
		{
			Contract.Assert( needle != null );

			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( @this.Length );

			return @this.Find( needle, _start, _end );
		}

		[DebuggerStepThrough]
		public static Int32 Find( this Byte[] @this, Byte[] needle, Int32 start, Int32 end )
		{
			Contract.Assert( needle != null );

			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( end < 0 ) ? ( @this.Length + end ) : ( end );

			var needlePos = 0;

			for( var i = _start; i < _end; i++ )
			{
				if( @this[i] == needle[needlePos] )
				{
					if( ++needlePos == needle.Length )
					{
						return i - needlePos + 1;
					}
				}
				else
				{
					needlePos = 0;
				}
			}

			return -1;
		}

		[DebuggerStepThrough]
		public static Int32[] FindAll( this Byte[] @this, Byte[] needle, Int32 start = 0 )
		{
			Contract.Assert( needle != null );

			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( @this.Length );

			return @this.FindAll( needle, _start, _end );
		}

		[DebuggerStepThrough]
		public static Int32[] FindAll( this Byte[] @this, Byte[] needle, Int32 start, Int32 end )
		{
			Contract.Assert( needle != null );

			var _start = ( start < 0 ) ? ( @this.Length + start ) : ( start );
			var _end = ( end < 0 ) ? ( @this.Length + end ) : ( end );

			var matches = new List<Int32>( 32 );

			var needlePos = 0;

			for( var i = _start; i < _end; i++ )
			{
				if( @this[i] == needle[needlePos] )
				{
					if( ++needlePos == needle.Length )
					{
						matches.Add( i - needlePos + 1 );
						needlePos = 0;
					}
				}
				else
				{
					needlePos = 0;
				}
			}

			return matches.ToArray();
		}

		/// <remarks>Credit to CodesInChaos's answer at http://stackoverflow.com/a/14333437/48700 on StackOverflow</remarks>
		[DebuggerStepThrough]
		public static String ToHexString( this Byte[] @this, Boolean upperCase = true )
		{
			var c = new Char[@this.Length * 2];

			int b;
			for( var i = 0; i < @this.Length; i++ )
			{
				b = @this[i] >> 4;
				c[i * 2] = (Char)( 55 + b + ( ( ( b - 10 ) >> 31 ) & -7 ) );
				b = @this[i] & 0xF;
				c[i * 2 + 1] = (Char)( 55 + b + ( ( ( b - 10 ) >> 31 ) & -7 ) );
			}

			return ( upperCase ) ? new String( c ) : new String( c ).ToLower();
		}
	}
}
